using System;
using System.Collections.Generic;
using System.Linq;
using D3;
using NYTBD.Common;
using NYTBD.Modules;

namespace NYTBD
{
    public class CachedPlayer
    {
        public static int LifeChangeTreshold = 10;

        public int Life;
        public int MaxLife;
        public int MaxPrimaryResource;
        public int MaxSecondaryResource;
        public int PrimaryResource;
        public int SecondaryResource;
        public int LifePercent;

        public CachedPlayer()
        {
            Life = (int) Me.Life;
            MaxLife = (int) Me.MaxLife;
        }

        public bool Update()
        {
            int tmpPercent = (Life*100/MaxLife);
            var lifePercNew = (int) (Me.Life*100/Me.MaxLife);

            if (Math.Abs(lifePercNew - tmpPercent) > LifeChangeTreshold)
            {
                Life = (int) Me.Life;
                MaxLife = (int) Me.MaxLife;
                return true;
            }

            else return false;
        }
    }

    public class CachedElement
    {
        public UIElement _me;
        public long Hash;
        public string Name;
        public string Text;
        public bool Visible;

        public void Click()
        {
            if (_me != null) _me.Click();
        }

        public CachedElement(UIElement ui)
        {
            _me = ui;
            Name = ui.Name;
            //Text = ui.Text;
            Hash = ui.Hash;
            Visible = ui.Visible;
        }

        public bool Update(UIElement ui)
        {
            bool ret = (ui.Visible != Visible) && Visible;
            Visible = ui.Visible;
            return ret;
        }
    }

    public class CachedUnit
    {
        public uint Ann;
        public SNOAnimId AnimationId;
        public int Life;
        public int MaxLife;
        public String Name;
        public UnitType UnitType;
        public float X;
        public float Y;
        public bool InRange;
        public Unit u;
        public bool UnitIsHelper;
        public bool UnitIsInvulnerable;
        public bool UnitIsNpc;
        public SNOActorId ActorId;

        public CachedUnit(Unit u)
        {

            this.u = u;
            if (u == null)
            {

                return;
            }
            if (u.Name != null) Name = u.Name;

            if (GetDistance(u.X, u.Y) > 1)
            {
                X = u.X;
                Y = u.Y;
            }

            AnimationId = u.AnimationId;
            Life = (int) u.Life;
            MaxLife = (int) u.MaxLife;
            Ann = u.ANN;
            UnitType = u.Type;

            ActorId = u.ActorId;
            int isNpc = u.GetAttributeInteger(UnitAttribute.Is_NPC);
            int isHelper = u.GetAttributeInteger(UnitAttribute.Is_Helper);
            int isInvul = u.GetAttributeInteger(UnitAttribute.Invulnerable);

            if (isNpc == 0) UnitIsNpc = false;
            else UnitIsNpc = true;
            if (isHelper == 0) UnitIsHelper = false;
            else UnitIsHelper = true;
            if (isInvul == 0) UnitIsInvulnerable = false;
            else UnitIsInvulnerable = true;
        }

        public bool update(Unit u)
        {
            if (GetDistance(u.X, u.Y) > 1)
            {
                X = u.X;
                Y = u.Y;
            }

            if (u.Name != null) Name = u.Name;
            AnimationId = u.AnimationId;
            Life = (int) u.Life;
            MaxLife = (int) u.MaxLife;
            Ann = u.ANN;
            UnitType = u.Type;
            ActorId = u.ActorId;

            int isNpc = u.GetAttributeInteger(UnitAttribute.Is_NPC);
            int isHelper = u.GetAttributeInteger(UnitAttribute.Is_Helper);
            int isInvul = u.GetAttributeInteger(UnitAttribute.Invulnerable);

            if (isNpc == 0) UnitIsNpc = false;
            else UnitIsNpc = true;
            if (isHelper == 0) UnitIsHelper = false;
            else UnitIsHelper = true;
            if (isInvul == 0) UnitIsInvulnerable = false;
            else UnitIsInvulnerable = true;

            return (u != null && u.Name != null);
        }

        public Unit GetUnit()
        {
            return u; //Unit.Get().Where(x => x.ANN == this.ANN).FirstOrDefault();
        }

        public float GetDistance()
        {
            return (float) Math.Sqrt(Math.Pow(X - Me.X, 2) + Math.Pow(Y - Me.Y, 2));
        }

        public float GetDistance(float ox, float oy)
        {
            return (float) Math.Sqrt(Math.Pow(X - ox, 2) + Math.Pow(Y - oy, 2));
        }
    }

    internal static class _Game
    {
        #region Delegates

        public delegate void AnimationChangeEventHandler(CachedUnit args);

        public delegate void AreaChangeEventHandler(SNOLevelArea s);

        public delegate void PlayerLifeChangeEventHandler(CachedPlayer p);

        public delegate void UnitEnteringRangeEventHandler(CachedUnit creep, float Range);

        public delegate void UnitSpawnEventHandler(CachedUnit creep, Boolean visible);

        public delegate void UIElementBecameVisibleHandler(ulong elem, bool visible);



        #endregion


        private static int _perfA;
        private static Logger _logger;
        private static float _distance = -1.0f;

        private static int _updateStart = Environment.TickCount;

        private static SNOLevelArea _currentArea = SNOLevelArea.Limbo;

        public static bool IsAlive(uint ann)
        {
            return _allUnits.ContainsKey(ann);
        }

        private static Dictionary<SNOAnimId, SNOAnimId> _trackedAnimations;

        private static Dictionary<uint, CachedUnit> _allUnits;
        private static Dictionary<long, CachedElement> _uiElements;
        private static Dictionary<ulong, bool> _trackedUiElements; 
        
        private static List<CachedUnit> _hostiles;
        private static List<CachedUnit> _items;
        public static CachedUnit ClosestHostile = null;
        public static CachedUnit ClosestItem = null;
        public static CachedUnit Me = null;

        public static bool Updating;

        static _Game()
        {
            Init();
        }

        public static void CheckPerformance(String text)
        {
            _logger.Log(text + ": " + (Environment.TickCount - _perfA));
            _perfA = Environment.TickCount;
        }

        public static event AnimationChangeEventHandler OnAnimationChange;

        public static event UnitSpawnEventHandler OnCreepSpawn;

        public static event AreaChangeEventHandler OnAreaChange;

        public static event UnitEnteringRangeEventHandler OnCreepEnteringRange;

        public static event UIElementBecameVisibleHandler OnUIElementVisible;

        public static event PlayerLifeChangeEventHandler OnPlayerLifeChange;


        public static int noHostiles()
        {
            return _hostiles.Count;
        }
        public static void Init()
        {
            _logger = new Logger("D3.Common.Dispatcher");

            _allUnits = new Dictionary<uint, CachedUnit>();
            _uiElements = new Dictionary<long, CachedElement>();
            _trackedUiElements = new Dictionary<ulong, bool>();

            _trackedAnimations = new Dictionary<SNOAnimId, SNOAnimId>();
            _hostiles = new List<CachedUnit>();
            _items = new List<CachedUnit>();


            Game.OnTickEvent += new TickEventHandler(Game_OnTickEvent);
        }


        public static void ToogleListeningForDistance(float f, bool @on)
        {
            if (@on) _distance = f;
            else _distance = -1.0f;
        }

        public static void TrackAnimation(SNOAnimId s, bool @on)
        {
            if (!_trackedAnimations.ContainsKey(s) && @on) _trackedAnimations.Add(s, s);
            else if (_trackedAnimations.ContainsKey(s) && !@on) _trackedAnimations.Remove(s);
        }


        public static Unit GetHostile(uint ann)
        {
            IEnumerable<CachedUnit> u = _hostiles.Where(x => x.Ann == ann);
            bool any = u.Any();
            return any ? u.First().u : null;
        }

        public static Unit GetHostile(String name)
        {
            IEnumerable<CachedUnit> u = _hostiles.Where(x => x.Name.Contains(name));
            bool any = u.Any();
            return any ? u.First().u : null;
        }


        public static Unit GetHostile(SNOActorId sno)
        {
            IEnumerable<CachedUnit> u = _hostiles.Where(x => x.ActorId == sno);
            bool any = u.Any();
            return any ? u.First().u : null;
        }

        public static Unit GetUnit(uint ann)
        {
            if (_allUnits.ContainsKey(ann)) return _allUnits[ann].u;
            else return null;
        }

        public static Unit GetUnit(String name)
        {
            IEnumerable<CachedUnit> u = _allUnits.Values.Where(x => x.Name.Contains(name));
            bool any = u.Any();
            return any ? u.First().u : null;
        }


        public static Unit GetUnit(SNOActorId sno)
        {
            IEnumerable<CachedUnit> u = _allUnits.Values.Where(x => x.ActorId == sno);
            bool any = u.Any();
            return any ? u.First().u : null;
        }

        public static bool AreaContainsCreeps(float originX, float originY, float range)
        {
            var list =
                new List<CachedUnit>(
                    _hostiles.Where(
                        x => IsHostile(x) && x.GetDistance(originX, originY) < range && x.u != null && x.u.Name != null));
            return list.Count > 0;
        }

        public static List<CachedUnit> GetCreepsInArea(float originX, float originY, float range)
        {
            var list =
                new List<CachedUnit>(
                    _hostiles.Where(
                        x => IsHostile(x) && x.GetDistance(originX, originY) < range && x.u != null && x.u.Name != null));
            return list;
        }

        public static CachedUnit GetClosestCreep()
        {
            float minDist = float.MaxValue;
            CachedUnit minCreep = null;

            foreach (var c in _hostiles)
            {
                if (c.GetDistance() < minDist)
                {
                    minDist = c.GetDistance();
                    minCreep = c;
                }
            }

            return minCreep;
        }


        public static bool IsHostile(CachedUnit creep)
        {
            return (creep.MaxLife > 1 && creep.Life > 0 && creep.UnitType == UnitType.Monster && !creep.UnitIsNpc &&
                    !creep.UnitIsHelper && !creep.UnitIsInvulnerable);
        }

        private static bool AreaChanged()
        {
            bool returnValue = false;
            String s = "" + D3.Me.LevelArea;
            String s2 = "" + _currentArea;
            if (!s.Equals(s2)) returnValue = true;
            _currentArea = D3.Me.LevelArea;
            return returnValue;
        }

        private static bool AnimationChanged(CachedUnit cachedUnit, Unit newUnit)
        {
            if (cachedUnit.AnimationId != newUnit.AnimationId)
            {
                cachedUnit.AnimationId = newUnit.AnimationId;
                if (_trackedAnimations.ContainsKey(newUnit.AnimationId))
                {
                    return true;
                }
            }
            return false;
        }

        private static bool UpdateRange(CachedUnit cachedUnit)
        {
            bool returnValue = false;

            float mDistNew = cachedUnit.GetDistance();

            if (mDistNew < _distance && !cachedUnit.InRange)
            {
                cachedUnit.InRange = true;
                returnValue = true;
            }

            if (mDistNew > _distance)
            {
                cachedUnit.InRange = false;
            }
            return returnValue;
        }

        private static void EnterUpdateLoop()
        {
            lock ("_Game._update")
            {
                _updateStart = Environment.TickCount;
                Updating = true;
            }
        }

        private static bool InUpdate()
        {
            lock ("_Game._update")
            {
                return Updating;
            }
        }

        private static void LeaveUpdateLoop()
        {
            lock ("_Game._update")
            {
                Updating = false;
            }
        }

        public static void TrackUIElement(ulong hash)
        {
            _trackedUiElements.Add(hash,false);
        }

        private static void Game_OnTickEvent(EventArgs e)
        {

            /*foreach (var var in UIElement.Get().Where(x => x.Text.Contains("SHATTERED")))
            {


            }*/

            if (!Game.Ingame || InUpdate() || _updateStart + 200 > Environment.TickCount) return;

            int a = Environment.TickCount;

            EnterUpdateLoop();

            Me = new CachedUnit(Unit.LocalPlayer);

            var currentUnits = Unit.Get();

            foreach (var trackedUiElement in _trackedUiElements.Keys)
            {
                UIElement ui = UIElement.Get(trackedUiElement);
                if (ui != default(UIElement))
                {
                    if (!_trackedUiElements[trackedUiElement])
                    {
                        _trackedUiElements[trackedUiElement] = true;
                        OnUIElementVisible(trackedUiElement, true);
                    }
                }
                else
                {
                    if (_trackedUiElements[trackedUiElement])
                    {
                        _trackedUiElements[trackedUiElement] = false;
                        OnUIElementVisible(trackedUiElement, false);

                    }
                }
            }

            foreach (
                var currentUnit in
                    currentUnits.Where(
                        currentUnit => currentUnit.Name != null && currentUnit.Name != D3.Me.Name && !currentUnit.Name.Contains("Blizzcon")))
            {
                CachedUnit cachedUnit;

                if (currentUnit.Name == null) continue;
                if (currentUnit.Name.Length <= 1) continue;
               
                if (!_allUnits.ContainsKey(currentUnit.ANN))
                {
                    cachedUnit = new CachedUnit(currentUnit);

                    _allUnits[cachedUnit.Ann] = cachedUnit;

                    //OnCreepSpawn(cachedUnit, true);
                }
                else
                {
                    cachedUnit = _allUnits[currentUnit.ANN];

                    if (AnimationChanged(cachedUnit, currentUnit)) OnAnimationChange(cachedUnit);

                    cachedUnit.update(currentUnit);
                }

                if (UpdateRange(cachedUnit)) OnCreepEnteringRange(cachedUnit, cachedUnit.GetDistance());
            }

            _allUnits =  _allUnits.Where(pair => Rem(pair.Value)).ToDictionary(pair => pair.Key,  pair => pair.Value);
            _hostiles = _allUnits.Values.Where(IsHostile).ToList<CachedUnit>();
            _items = _allUnits.Values.Where(x=> x.u.ItemContainer == Container.Unknown).ToList<CachedUnit>();

            UpdateClosestCreep();
            UpdateClosestItem();

            LeaveUpdateLoop();
            
        }


        private static void UpdateClosestItem()
        {

            CachedUnit cachedUnit = null;
            float minDist = float.MaxValue;

            foreach (var item in _items.Where(x=> x.u.ItemType == UnitItemType.Gold || x.u.ItemQuality > UnitItemQuality.Magic1))
            {
                uint it = (uint)item.UnitType;
                uint i = (uint)UnitType.Item;
                bool res = it == i;

                if (item.GetDistance() < minDist && res)
                {
                    minDist = item.GetDistance();
                    cachedUnit = item;
                }
            }
            //if ( cachedUnit != null ) Game.Print(cachedUnit.u.Name);
            ClosestItem = cachedUnit;
          
        }

        private static void UpdateClosestCreep()
        {


            CachedUnit cachedUnit = null;
            float minDist = float.MaxValue;

            foreach (var hostile in _hostiles)
            {

                if (hostile.GetDistance() < minDist)
                {
                    minDist = hostile.GetDistance();
                    cachedUnit = hostile;
                }
            }
            
            ClosestHostile = cachedUnit;

            //if (ClosestHostile != null) _logger.Log(ClosestHostile.Name);

        }

        private static bool Rem(CachedUnit u)
        {
            return (u.u.Name != null);
        }
    }
}